<?php

namespace Hilaoyu\LaravelExtend\Services\Files;
/**
 * Created by laoyu.
 * User: hilaoyu@qq.com
 * Date: 2018/2/26
 * Time: 15:34
 */


use Hilaoyu\LaravelExtend\Supports\FileQueue;
use Hilaoyu\Utils\Traits\ErrorsTrait;
use Hilaoyu\Utils\UtilArr;
use Hilaoyu\Utils\UtilCli;
use Hilaoyu\Utils\UtilFile;
use Hilaoyu\Utils\UtilHttp;
use Hilaoyu\Utils\UtilStr;
use Hilaoyu\Utils\UtilSupport;
use Illuminate\Routing\Middleware\SubstituteBindings;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Image;
use Hilaoyu\LaravelExtend\Supports\Uuid;
use Illuminate\Support\Facades\Route;
use FFMpeg;
use SoareCostin\FileVault\Facades\FileVault;
use Sunra\PhpSimple\HtmlDomParser;
use Symfony\Component\HttpFoundation\BinaryFileResponse;

class FileService
{
    use ErrorsTrait;

    protected $fs_disk = '';
    protected $path = '';
    protected $files_model = null;
    protected $secret = null;
    protected $mime_type_form_remote_cache = null;

    static protected $_self_class_cache = [];


    /**
     * @param $disk
     * @return FilesApiClient|null
     */
    static public function useAsClient($disk)
    {
        if ($client_config = static::getDiskConfig($disk, 'use_as_client')) {
            return new FilesApiClient($client_config);
        }
        return null;
    }

    static public function tempDisk()
    {
        return '_upload_temp';
    }

    static public function fileUseQueue()
    {
        return FileQueue::make('laravel_extend_files_use_file_queue_'. config('app.env',''));
    }


    /**
     * @return string
     */
    static public function classNameFilesModel()
    {
        $files_model_name = config('file_service.files_model');
        if (!$files_model_name) {
            $files_model_name = FilesVirtualModel::class;
        }
        return $files_model_name;
    }

    static public function newFilesModel($disk, $attributes = [])
    {
        $attributes = array_merge([
            'path' => static::generateHashPath(),
            'upload_task' => [
                'chunks' => 0,
                'chunk' => 0,
                'chunks_completed' => [],
                'chunks_completed_count' => 0,
                'chunks_completed_percent' => 0,
                'chunk_size' => 0,
            ],
            'upload_is_completed' => 0,
        ], $attributes);
        $attributes['disk'] = $disk;
        $filesModel = static::loadFilesModel('', $attributes);


        return $filesModel;
    }

    /**
     * @param string $id
     * @return FilesModelAbstract
     */
    static public function loadFilesModel($id = '', $attributes = [])
    {
        if ($id instanceof FilesModelAbstract) {
            $model = $id;
        } else {
            $id = trim($id);
            $model = UtilSupport::makeClass(static::classNameFilesModel());
            if ($model && Uuid::verify($id)) {
                $model = $model->find($id);
                if (!$model) {
                    $model = static::classNameFilesModel()::loadCacheModel($id);
                }
            }
        }

        if ($model && !empty($attributes)) {
            $model->fill($attributes);
        }

        return $model;
    }


    /**
     * 通过自定义格式的文件路径生成当前类实例
     * @param $fs_path
     * @return static
     */
    static public function make($fs_path)
    {
        $fs_path = ltrim(str_replace('\\', '/', $fs_path), '/');
        $fs_path = trim($fs_path);

        if (!array_key_exists($fs_path, static::$_self_class_cache)) {
            $disk = '';
            $path = $fs_path;
            $files_model = null;

            $config_disks = config('filesystems.disks', []);

            if (strpos($fs_path, '::')) {
                @list($disk, $path) = explode('::', $fs_path, 2);
                !$disk && $disk = '';
                !$path && $path = '';
            } else {
                $temp_path = parse_url($fs_path, PHP_URL_PATH);
                $temp_path = ltrim($temp_path, '/');

                $view_prefix = config('file_service.view.prefix');
                $view_prefix = ltrim($view_prefix, '/');
                if (Str::startsWith($temp_path, $view_prefix . '/view/')) {
                    $tmp = Str::after($temp_path, $view_prefix . '/view/');
                    $disk = Str::before($tmp, '/');
                    $path = Str::after($tmp, '/');
                    if (!config('filesystems.disks.' . $disk)) {
                        $disk = '';
                    }
                } elseif (Str::startsWith($temp_path, $view_prefix . '/file/')) {
                    $id = Str::after($temp_path, $view_prefix . '/file/');
                    $id = trim($id,'/');

                    if (Uuid::verify($id)) {
                        $files_model = static::loadFilesModel($id);

                        if ($files_model) {
                            $disk = $files_model->getAttribute('real_disk');
                            $path = $files_model->getAttribute('path');
                        }
                    }
                }

            }

            $instrface = new static($disk, $path, $files_model);

            static::$_self_class_cache[$fs_path] = $instrface;
        }

        return isset(static::$_self_class_cache[$fs_path]) ? static::$_self_class_cache[$fs_path] : null;


    }

    static public function makeTempFile($ext='',$ttl=null){
        $disk = static::tempDisk();

        $path = static::generateHashPath(null,null,$ext);

        $fs =  new static($disk,$path);
        $dir = UtilFile::dirname($fs->path(true));
        UtilFile::checkDir($dir);
        $fs->filesModelSaveToCache($ttl);
        return $fs;
    }


    static public function getDiskConfig($disk, $key = '', $default = null)
    {
        $disk = trim($disk);
        if (empty($disk)) {
            return $default;
        }

        $config = [];
        if ($disk) {
            $config = config('filesystems.disks.' . $disk, $default);

        }


        $key = trim($key);

        return $key ? data_get($config, $key, $default) : $config;
    }

    /**
     * 自动生成散列的文件路径和随机文件名
     * @param null $path
     * @param null $name
     * @param null $ext
     * @return string
     */
    static public function generateHashPath($path = null, $name = '', $ext = '')
    {
        $name = trim($name);
        $ext = trim($ext);


        if (is_null($path)) {
            $path = date('Y') . '/' . date('m');
        }
        if ('' == $name) {
            $name = uniqid(date('YmdHis_'));
        }

        if ('' == $ext) {
            $ext = UtilFile::getExtension($name);
            $name = UtilFile::getNameWithOutExtension($name);
        }

        $path = $path . '/' . $name;

        if (is_string($ext) && $ext != '') {
            $path .= '.' . $ext;
        }


        $path = trim($path);
        $path = ltrim($path, '/');
        $path = UtilStr::clearRepeatedChar($path, ['/', '\\'], '/');

        return $path;
    }

    /**
     * @param $disk
     * @return \Illuminate\Contracts\Filesystem\Filesystem|\League\Flysystem\Filesystem|null
     */
    static public function makeStorage($disk = '')
    {
        $disk = trim($disk);

        empty($disk) && $disk = static::tempDisk();

        if (empty($disk)) {
            return null;
        }

        try {
            app('filesystem')->forgetDisk($disk);
            return Storage::disk($disk);
        } catch (\Exception $e) {
            return null;
        }
        return null;
    }

    static public function contentFiles($content)
    {
        $urls = [];
        $pattern = "/([\s]+src[\s]*=[\s]*[\"'])(.*?)([\"'])/isS";
        preg_match_all($pattern, $content, $matches, PREG_SET_ORDER);
        foreach ($matches as $match) {
            $urls[] = $match[2];
        }

        return $urls;
    }

    static public function contentFsUseFor($content)
    {

        $urls = static::contentFiles($content);
        foreach ($urls as $url) {
            if ($url && $Fs = static::make($url)) {

                if ($newFs = $Fs->queueUseFor()) {
                    //$fs_path = $newFs->filesModel('url');
                }

            }
        }


        return $content;
    }

    static public function contentFsDelete($content)
    {

        $urls = static::contentFiles($content);
        foreach ($urls as $url) {
            $Fs = static::make($url);
            if ($Fs) {
                $Fs->delete();
            }
        }

        return $content;
    }


    /**
     * 构造方法
     * Service constructor.
     * @param string $fs_disk
     * @param string $path
     * @param string $uri
     */
    public function __construct($fs_disk = '', $path = '', $files_model = null)
    {
        $fs_disk = trim($fs_disk);
        $path = trim($path);


        $this->files_model = $files_model;


        if (!$fs_disk) {
            $fs_disk = $this->filesModel('real_disk', '');
        }
        if (!$path) {
            $path = $this->filesModel('path', '');
        }
        if ($fs_disk != '') {
            $this->fs_disk = $fs_disk;
        }
        if ($path != '') {
            $this->path = $path;
        }


        if (!$files_model) {
            $files_model = static::newFilesModel($this->disk(), [
                'name' => UtilFile::basename($this->path()),
                'path' => $this->path(),
                'type' => UtilFile::mineToNormalType($this->mimeType()),
                'ext' => $this->extension(),
                'mime' => $this->mimeType(),
                'is_permanent' => '1',
            ]);
            if ($this->disk() && $this->path() && !$files_model instanceof FilesVirtualModel) {
                if (
                $files_model_temp = $files_model->newQuery()
                    ->where('disk', '=', $fs_disk)
                    ->where('path', '=', $path)
                    ->first()
                ) {
                    $files_model = $files_model_temp;
                }
            }

            $this->files_model = $files_model;
        }

        $this->setSecretBase64($this->filesModel('secret'));

    }


    /**
     * 返回存储区对象
     * @param string $disk
     * @return \Illuminate\Contracts\Filesystem\Filesystem|\League\Flysystem\Filesystem|null
     */
    public function storage($disk = '')
    {
        $disk = trim($disk);
        if (empty($disk)) {
            $disk = $this->fs_disk;
        }

        if (empty($disk)) {
            $this->setError('存储区配置错误');
            return null;
        }

        return static::makeStorage($disk);
    }

    /**
     * 当前文件是否存在
     * @param string $path
     * @return bool
     */
    public function exists($path = '')
    {
        $path = trim($path);
        empty($path) && $path = $this->path;


        if (empty($path)) {
            return false;
        }

        if ($storage = $this->storage()) {
            return $storage->has($path);
        }

        return false;
    }

    /**
     * 返回当前存储区名
     * @return string
     */
    public function disk()
    {
        return $this->fs_disk;
    }

    public function setDisk($disk)
    {
        $disk = trim($disk);
        $this->disk = $disk;
        return $this;
    }

    /**
     * 返回当前文件的路径
     * @param bool $realPath
     * @return string
     */
    public function path($realPath = false)
    {

        $path = $this->path;
        if ($realPath && $storage = $this->storage()) {
            $path = $this->storage()->path($this->path);
        }


        return $path;
    }

    /**
     * 设置文件相对路径
     * @param String $path //相对于存储区的路径
     * @return $this
     */
    public function setPath($path)
    {
        $path = trim($path);
        $this->path = $path;
        $this->filesModelFill(['path' => $path]);
        return $this;
    }

    /**
     * 返回当前文件的网址
     * @return string
     */
    public function url($absolute = false)
    {
        $url = $this->path();

        if ($this->filesModel('id')) {
            $url = \route('web.l-e-s.files.file', ['id' => $this->filesModel('id')], $absolute);
        } elseif ($this->disk()) {
            if ('local' == static::getDiskConfig($this->disk(), 'driver')) {

                $url = \route('web.l-e-s.files.view', ['disk' => $this->disk(), 'path' => $this->path()], $absolute);
            } else {
                $url = $this->storage()->url($this->path());
            }

        }

        return $url;
    }

    /**
     * 返回当前文件的临时网址
     * @param null $ttl 有效时间
     * @param false $absolute
     * @return array|false|mixed|string|null
     */
    public function tempUrl($ttl=null,$absolute = false)
    {
        if($client = static::useAsClient($this->disk())){
            $url =  $client->tempUrl([
                'id'=>$this->filesModel('id'),
                'path'=>$this->path(),
                'ttl'=>(string)$ttl,
            ]);

            if(!$url){
                $this->setError($client->getResult('message'));
                $this->setError($client->getError());
            }
            return $url;
        }


        $tmpFilesModel = static::newFilesModel($this->disk(),$this->files_model->setHidden([])->attributesToArray());
        $tmpFilesModel->saveToCache($ttl);



        return \route('web.l-e-s.files.file', ['id' => $tmpFilesModel->getAttribute('id')], $absolute);
    }

    public function hash(string $algo = 'md5')
    {
        if (!$this->exists()) {
            return '';
        }
        return hash_file($algo, $this->path(true));
    }

    /**
     * @return bool|BinaryFileResponse
     */
    public function response($name = '')
    {
        $name = $name ? $name : ($this->filesModel('name',  UtilFile::basename($this->path())));
        if (!Str::endsWith($name, '.' . $this->extension())) {
            $name .= '.' . $this->extension();
        }
        $mime_type = $this->mimeType();

        if ('local' == static::getDiskConfig($this->disk(), 'driver')) {

            if (!$this->exists()) {
                abort(404);

            }

            if ($secret = $this->filesModel('secret')) {


                $response = $this->setSecretBase64($secret)->responseDecrypt($name);
                $mime_type = $this->filesModel('type', '');
            } else {

                $response = response()->download($this->path(true),$name);
                /*$response = BinaryFileResponse::create(
                    $this->path(true)
                    , 200
                    , []
                    , true
                    , null //'attachment; filename='.($name ? $name : UtilFile::basename($this->path()))
                    , true
                    , true
                );*/

            }


            $request = request();
            $response->prepare($request);

            $response->headers->set('Content-Disposition', 'filename=' . $name);
            if ($mime_type) {

                $response->headers->set('Content-Type', $mime_type);
            }
            return $response;
        } else {
            if ($storage = $this->storage()) {
                return $storage->response($this->path, $name);
            }
        }


        abort(404);
    }

    public function responseDecrypt($name = '')
    {
        $name = $name ? $name : ($this->filesModel('name', '', UtilFile::basename($this->path())));
        if (!Str::endsWith($name, '.' . $this->extension())) {
            $name .= '.' . $this->extension();
        }

        return response()->stream(function () {
            return FileVault::disk($this->disk())->key($this->getSecret())->streamDecrypt($this->path());
        }, 200, [
            'Content-Type' => $this->mimeType() ?? 'application/octet-stream',
            'Content-Disposition' => 'filename=' . $name,
        ]);
        //, UtilFile::basename($this->path()

    }

    /**
     * 文件转base64
     * @param bool $mime_type_prefix 是否需要mime type 前辍
     * @return string
     */
    public function base64($mime_type_prefix = true)
    {
        $base64_str = '';
        try {
            if ($this->exists()) {
                $content = $this->storage()->read($this->path);
                if ($content) {
                    $base64_str = base64_encode($content);
                }
            }

            if ($base64_str && $mime_type_prefix && ($mime_type = $this->mimeType())) {
                $base64_str = 'data:' . $mime_type . ';base64,' . $base64_str;
            }
        } catch (\Exception $exception) {

        }


        return $base64_str;
    }

    /**
     * 返回文件的mome type
     * @return bool|false|string
     */
    public function mimeType()
    {
        $mime_type = '';
        if ($this->exists()) {
            $mime_type =  $this->storage()->mimeType($this->path());
        } elseif (filter_var($this->path(), FILTER_VALIDATE_URL)) {
            if (!$this->mime_type_form_remote_cache) {
                $httpClient = new UtilHttp();
                $httpClient->setConnectTimeout(10);
                $httpClient->setTimeout(10);
                $headers = $httpClient->httpRequestHeaderOnly($this->path(), [], 'get');
                $this->mime_type_form_remote_cache = data_get($headers, 'mimetype', '_other');
            }
            $mime_type = $this->mime_type_form_remote_cache;
        }

        if('image/svg' == $mime_type) {
            $mime_type .= '+xml';
        }
        return $mime_type;
    }


    public function extension()
    {
        return UtilFile::getExtension($this->path());
    }

    public function timestamp()
    {
        if ($this->exists()) {
            return $this->storage()->getTimestamp($this->path());
        }

        return null;
    }

    public function lastModified()
    {
        if ($this->exists()) {
            return $this->storage()->lastModified($this->path());
        }

        return null;
    }


    /**
     * 删除当前文件
     * @return bool
     */
    public function delete($delModel=true)
    {


        if ($this->exists() && is_dir($this->path(true))) {
            UtilFile::delDirOrFile($this->path(true));
        } elseif (($storage = $this->storage()) && $this->path) {
            $storage->delete($this->path);
        }
        if ($delModel && $this->files_model && $this->files_model->getKey()) {
            $this->files_model->deleteCache();
            $this->files_model->delete();
        }
        return true;
    }

    public function setSecret($secret = null)
    {
        $this->secret = $secret;
        return $this;
    }

    public function setSecretBase64($secret = '')
    {
        if ($secret) {
            UtilSupport::tryQuiet(function () use ($secret) {
                $this->setSecret(base64_decode($secret));
            });

        }
        return $this;
    }

    public function getSecret()
    {
        return $this->secret;
    }

    public function getSecretBase64()
    {
        if ($secret = $this->getSecret()) {
            return base64_encode($secret);
        }
        return '';
    }

    static public function generateSecret()
    {
        return \SoareCostin\FileVault\FileVault::generateKey();
    }

    public function encrypt($secret = null, string $path = null, $deleteSource = true)
    {
        if (!$this->disk()) {
            return $this;
        }
        if (!$secret) {
            $secret = static::generateSecret();
        }
        if (!$path) {
            $path = static::generateHashPath(null, null, $this->extension());
        }
        UtilFile::checkDir(UtilFile::dirname($this->storage()->path($path)));

        $status = UtilSupport::tryQuiet(function () use ($secret, $path, $deleteSource) {
            return FileVault::disk($this->disk())->key($secret)->encrypt($this->path(), $path, $deleteSource);
        });

        if (!$status) {
            return false;
        }

        $newFs = clone $this;
        $newFs->setSecret($secret);
        $newFs->setPath($path);
        $files_model = $newFs->filesModel();
        if ($files_model && $files_model->getKey()) {
            $new_files_model = $files_model;
            if (!$deleteSource) {
                $new_files_model = static::loadFilesModel('', $files_model->setHidden('id')->setAppends([])->attributesToArray());
            }

            $new_files_model->fill([
                'disk' => $this->disk(),
                'path' => $path,
                'secret' => $newFs->getSecretBase64(),
                'is_permanent' => '1',
            ])->save();
            $newFs->setFilesModel($new_files_model);
        }

        return $newFs;
    }

    public function decrypt(string $path = null, $deleteSource = true)
    {
        if (!$this->disk()) {
            return $this;
        }

        if (!$path) {
            $path = static::generateHashPath(null, null, $this->extension());
        }
        UtilFile::checkDir(UtilFile::dirname($this->storage()->path($path)));
        //$status = FileVault::disk($this->disk())->key($this->getSecret())->decrypt($this->path(), $path, $deleteSource);
        $status = UtilSupport::tryQuiet(function () use ($path, $deleteSource) {
            return FileVault::disk($this->disk())->key($this->getSecret())->decrypt($this->path(), $path, $deleteSource);
        });

        if (!$status) {
            $this->storage()->delete($path);
            return false;
        }

        $newFs = clone $this;
        $newFs->setPath($path);
        $newFs->setSecret(null);
        $files_model = $newFs->filesModel();
        if ($files_model && $files_model->getKey()) {
            $new_files_model = $files_model;
            if (!$deleteSource) {
                $new_files_model = static::loadFilesModel('', $files_model->setHidden('id')->setAppends([])->attributesToArray());
            }

            $new_files_model->fill([
                'disk' => $this->disk(),
                'path' => $path,
                'secret' => '',
                'is_permanent' => '1',
            ])->save();
            $newFs->setFilesModel($new_files_model);
        }

        return $newFs;
    }

    public function queueUseFor($secret = null)
    {
        if (!$this->files_model || !$this->files_model->getKey()) {
            return $this;
        }
        //file_put_contents(storage_path("file_use_queue_".config('app.env','').'.txt'),"quere usefor model:".$this->files_model->toJson()."\n",FILE_APPEND);
        if ('1' == $this->filesModel('is_permanent')) {
            return $this;
        }

        static::fileUseQueue()->put([
            'disk' => $this->disk(),
            'path' => $this->path(),
            'file_model_id' => $this->filesModel('id'),
            'secret' => $secret ? base64_encode($secret) : null,
            'config_filesystems_disks' => config('filesystems.disks'),
        ]);

        $this->filesModelSave();
        return $this;
    }

    /**
     * 使用文件
     * @return $this|bool|FileService
     */
    public function useFor($secret = null)
    {
        //file_put_contents(storage_path("file_use_queue_".config('app.env','').'.txt'),"usefor config_disks:".json_encode(config("filesystems.disks"))."\n",FILE_APPEND);
        if (!$this->files_model || !$this->files_model->getKey()) {
            return $this;
        }

        if ('1' == $this->filesModel('is_permanent')) {
            return $this;
        }

        if ($client = static::useAsClient($this->filesModel('disk'))) {
            if ($attributes = $client->useFor($this->filesModel())) {
                unset($attributes['disk']);
                $this->filesModelFill($attributes);
                $this->files_model->save();
                $this->files_model->deleteCache();
            }else{
                //$this->files_model->delete();
                //$this->files_model->deleteCache();
            }
            return $this;
        }

        if (in_array($this->extension(), ['wmf'])) {

            try {

                $image_path = $this->path() . '.png';
                $image_path_real = $this->storage()->path($image_path);
                if (!UtilFile::checkDir(UtilFile::dirname($image_path_real))) {
                    throw new \Exception('存储目录不可写');
                }
                $image = \Image::make($this->path(true));
                $image->encode('png');
                $image->save($image_path_real);

                $this->setPath($image_path);
                unset($image);
            } catch (\Exception $exception) {
                //dump($exception->getMessage());
            }


        }

        $newFs = $this->moveTo($this->filesModel('disk'));

        if (!$newFs) {
            return $this;
        }

        $this->files_model->deleteCache();

        //encrypt

        if (!$secret) {
            return $newFs;
        }

        if ($secret == $newFs->getSecret()) {
            return $newFs;
        }
        $path = $newFs->path();

        if ($newFs->getSecret()) {
            $dePath = $path . '.' . uniqid('de_');
            $deFs = $newFs->decrypt($dePath, true);
            if (!$deFs) {
                return $newFs;
            }
        }


        $enPath = $path . '.' . uniqid('en_');;

        $enFs = $deFs->encrypt($secret, $enPath, true);


        if (!$enFs) {
            $deFs->delete();
            return false;
        }

        return $enFs;
    }

    /** 移动文件
     * @param $disk
     * @param null $path
     * @param null $name
     * @return static|null
     */
    public function moveTo($disk = '', $path = null, $name = null)
    {
        $disk = trim($disk);

        if (!$this->disk()) {
            return $this;
        }

        if (!$this->exists()) {
            $this->setError('文件不存在');
            return null;
        }


        empty($disk) && $disk = $this->fs_disk;
        if (!($ToStorage = $this->storage($disk))) {
            $this->setError('目的存储驱动错误');
            return null;
        }
        $ext = pathinfo($this->path, PATHINFO_EXTENSION);
        if ($name) {
            $name = rtrim($name, '.' . $ext);
        }
        $path = static::generateHashPath($path, $name, $ext);

        if ($this->path(true) == $ToStorage->path($path)) {
            return $this;
        }






        $Ex = false;
        //file_put_contents(storage_path("file_use_queue_".config('app.env','').'.txt'),"move disk:".$disk." config_disks:".json_encode(config("filesystems.disks"))."\n",FILE_APPEND);
        //file_put_contents(storage_path("file_use_queue_".config('app.env','').'.txt'),"move ".$this->path(true).":".$ToStorage->path($path)."\n",FILE_APPEND);
        if ('local' == static::getDiskConfig($this->disk(), 'driver') && 'local' == static::getDiskConfig($disk, 'driver')) {
            $ToPath = dirname($ToStorage->path($path));
            if (!UtilFile::checkDir($ToPath)) {
                $this->setError('目的存储目录不可写');
                return null;
            }
            $Ex = @rename($this->path(true), $ToStorage->path($path));
        } else {
            $Ex = $ToStorage->writeStream($path, $this->storage()->readStream($this->path()));

        }

        if (!$Ex) {
            $this->setError('移动失败');
            return null;

        }
        $this->delete(false); //删除本地文件

        $newFs = clone $this;
        $newFs->setDisk($disk);
        $newFs->setPath($path);
        if ($newFs->files_model && $newFs->files_model->getKey()) {
            $file_model = static::loadFilesModel($newFs->files_model->getKey());
            if($file_model){
                $status = $file_model->fill([
                    'disk' => $disk,
                    'path' => $path,
                    'is_permanent' => '1',
                ])->save();
                $newFs->files_model = $file_model;
            }
            //dd($status,$newFs->files_model->getError());
        }
        return $newFs;
    }

    /**
     * 复制文件
     * @param $disk
     * @param null $path
     * @param null $name
     * @return bool|static
     */
    public function copyTo($disk = '', $path = null, $name = null)
    {
        $disk = trim($disk);
        if (!$this->disk()) {
            return $this;
        }

        if (!$this->exists()) {
            return null;
        }


        empty($disk) && $disk = $this->fs_disk;

        if (!($ToStorage = $this->storage($disk))) {
            $this->setError('目的存储驱动错误');
            return null;
        }

        $ext = pathinfo($this->path, PATHINFO_EXTENSION);
        if ($name) {
            $name = rtrim($name, '.' . $ext);
        }
        $path = static::generateHashPath($path, $name, $ext);

        if ($this->path(true) == $ToStorage->path($path)) {
            return $this;
        }




        $Ex = false;

        if ('local' == static::getDiskConfig($this->disk(), 'driver') && 'local' == static::getDiskConfig($disk, 'driver')) {
            $ToPath = dirname($ToStorage->path($path));

            if (!UtilFile::checkDir($ToPath)) {
                $this->setError('目的存储目录不可写');
                return null;
            }
            $Ex = @copy($this->path(true), $ToStorage->path($path));
        } else {
            $Ex = $ToStorage->writeStream($path, $this->storage()->readStream($this->path()));
        }

        if (!$Ex) {
            $this->setError('复制失败');
            return null;

        }
        $newFs = clone $this;
        $newFs->setDisk($disk);
        $newFs->setPath($path);
        $files_model = $newFs->filesModel();
        if ($files_model && $files_model->getKey()) {
            $new_files_model = static::loadFilesModel('', $files_model->setHidden('id')->setAppends([])->attributesToArray());
            $new_files_model->fill([
                'disk' => $disk,
                'path' => $path,
                'is_permanent' => '1',
            ])->save();
            $newFs->setFilesModel($new_files_model);
        }
        return $newFs;
    }

    public function compress($zip_path, $password = '', $local_path = '/')
    {
        if (!$this->exists()) {
            $this->setError('要压缩的文件不存在');
            return false;
        }
        $status = UtilFile::compressZip($zip_path, $this->path(true), $password, $local_path, false);

        if (true === $status) {
            return true;
        }
        if (is_string($status)) {
            $this->setError($status);
        }

        return false;
    }

    /**
     * 解压当前文件
     * @param $to
     * @return bool
     */
    public function extract($to, $password = '')
    {
        if (!$this->exists()) {
            $this->setError('文件不存在');
            return false;
        }

        $to = trim($to);
        if (empty($to)) {
            $this->setError('解压到的目录不能为空');
            return false;
        }

        $status = UtilFile::extractZip($this->path(true), $to, $password);

        if (true === $status) {
            return true;
        }
        if (is_string($status)) {
            $this->setError($status);
        }

        return false;
    }

    /**
     * 生成缩略图
     * @param null $width 缩略图宽
     * @param null $height 缩略图高
     * @return static|string
     */
    public function imageMakeThumb($width = 320, $height = null)
    {
        if ($this->exists() && UtilStr::startsWith($this->mimeType(), 'image/')) {

            $width = intval($width);
            $height = intval($height);

            $path = $this->path();
            $secret = $this->getSecret();

            $str_width = $width ? strval($width) : '_';
            $str_height = $height ? strval($height) : '_';

            $small_path = UtilFile::getNameWithOutExtension($path) . ".small." . $str_width . '.' . $str_height . '.' . $this->extension();
            $small_real_path = $this->storage()->path($small_path);


            try {
                $thumbFs = $this;
                if (!is_file($small_real_path)) {
                    ini_set("memory_limit", "-1"); //不限制php使用内存
                    $image_path = $this->path(true);
                    $detempFs = null;
                    if ($secret) {


                        $detempFs = $this->decrypt($path . '.' . uniqid('detemp'), false);
                        if ($detempFs) {
                            $image_path = $detempFs->path(true);
                        }
                    }

                    $org_image = Image::make($image_path);
                    $need_resize = false;
                    if ($width > 0) {
                        $org_width = $org_image->getWidth();
                        if ($org_width > $width) {
                            $need_resize = true;
                        }
                        $height = null;
                    } elseif ($height > 0) {
                        $org_height = $org_image->getHeight();
                        if ($org_height > $height) {
                            $need_resize = true;
                        }
                        $width = null;
                    }

                    if ($need_resize) {
                        $org_image->resize($width, $height, function ($constraint) {
                            $constraint->aspectRatio();
                        })
                            ->save($small_real_path);

                    }

                    if ($detempFs) {
                        $detempFs->delete();
                    }

                    if (file_exists($small_real_path)) {
                        $thumbFs->setPath($small_path);
                        if ($thumbFs && $secret) {
                            $thumbEnPath = $thumbFs->path() . '.en';
                            $thumbEnPathReal = $thumbFs->storage()->path($thumbEnPath);
                            $thumbFs = $thumbFs->encrypt($secret, $small_path, true);
                        }

                    }
                } else {
                    $thumbFs->setPath($small_path);
                }


                return $thumbFs;

            } catch (\Exception $e) {
                $this->setError($e->getMessage());
                return $this;
            }


        }
        return $this;
    }

    /**
     * @param $source
     * @param int $transparency 透明度 %
     * @param int $sizeRatio 水印占图片大小的比例 %
     * @param string $position "top","middle","bottom" 与 "left","center","right" 单独或组合
     * @param int $margin_x
     * @param int $margin_y
     * @param bool $newImage 是否生成新图片
     * @return $this|static
     */
    public function imageWatermark($source, $transparency = 100, int $sizeRatio = 10, string $position = 'bottom-right', int $margin_x = 0, int $margin_y = 0, $newImage = true)
    {

        if (file_exists($source) && $this->exists() && Str::startsWith($this->mimeType(), 'image/')) {
            try {
                $path_with_watermark = $path = $this->path();
                $real_path_with_watermark = $this->path(true);

                if ($newImage) {
                    $path_with_watermark = $path . ".watermark." . $this->extension();
                    $real_path_with_watermark = $this->storage()->path($path_with_watermark);
                }

                ini_set("memory_limit", "-1"); //不限制php使用内存
                $org_image = Image::make($this->path(true));
                $org_width = $org_image->getWidth();
                $org_height = $org_image->getHeight();
                $mark_image = Image::make($source);
                $mark_image->opacity($transparency); //设置透明度
                $mark_width = $mark_image->getWidth();
                $mark_height = $mark_image->getHeight();

                $mark_resize_width = $mark_resize_height = null;

                if ($mark_width > $mark_height) {
                    $mark_resize_width = intval(min($org_width, $org_height) * $sizeRatio / 100);

                } else {
                    $mark_resize_height = intval(min($org_width, $org_height) * $sizeRatio / 100);
                }

                $mark_image->resize($mark_resize_width, $mark_resize_height, function ($constraint) {
                    $constraint->aspectRatio();
                });


                $org_image->insert($mark_image, $position, $margin_x, $margin_y)->save($real_path_with_watermark);


                return new static($this->disk(), $path_with_watermark);
            } catch (\Exception $e) {
                return $this;
            }


        }
        return $this;
    }

    public function videoMakeCover($from_seconds = 0, $save_file = '')
    {
        $save_file = trim($save_file);
        try {
            $media = FFMpeg::fromDisk($this->disk())->open($this->path());
            if (is_numeric($from_seconds)) {
                $frame = $media->getFrameFromSeconds(intval($from_seconds));
            } else {
                $frame = $media->getFrameFromString(trim($from_seconds));
            }


            $media_export = $frame->export();
            if ($save_file) {
                return $media_export->save($save_file);
            }

            return $frame;
        } catch (\Exception $e) {
            $this->setError($e->getMessage());

            return false;
        }

        return false;
    }



    public function setFilesModel($files_model)
    {
        if ($files_model instanceof FilesModelAbstract) {
            $this->files_model = $files_model;
        }
        return $this;
    }

    public function filesModel($key = '', $defualt = null)
    {
        $key = trim($key);
        if ($this->files_model) {

            return $key ? ($this->files_model->getAttribute($key)) : $this->files_model;
        }
        return $defualt;
    }

    public function filesModelFill(array $attributes = [])
    {
        if ($this->files_model) {

            $this->files_model->fill($attributes);
        }
        return $this;
    }

    public function filesModelSave()
    {
        if ($this->files_model) {
            $status = $this->files_model->save();
            if (!$status) {
                $this->setError($this->files_model->getError());
            }
            return $status;
        }
        return false;
    }

    public function filesModelSaveToCache($ttl = null)
    {
        if ($this->files_model) {
            $status = $this->files_model->saveToCache($ttl);
            if (!$status) {
                $this->setError($this->files_model->getError());
            }
            return $status;
        }
        return false;
    }
}
